/*
 Copyright (C) 2010 Kristian Duske

 This file is part of TrenchBroom.

 TrenchBroom is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 TrenchBroom is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "Macros.h"
#include "mdl/EntityNodeBase.h"
#include "mdl/EntityProperties.h"
#include "mdl/IdType.h"
#include "mdl/MapFormat.h"
#include "mdl/Node.h"
#include "octree.h"

#include <memory>
#include <vector>

namespace tb::mdl
{
class IssueQuickFix;
enum class MapFormat;
class PickResult;
class Validator;
class ValidatorRegistry;

class WorldNode : public EntityNodeBase
{
private:
  EntityPropertyConfig m_entityPropertyConfig;
  MapFormat m_mapFormat;
  LayerNode* m_defaultLayer;
  std::unique_ptr<ValidatorRegistry> m_validatorRegistry;

  using NodeTree = octree<double, Node*>;
  std::unique_ptr<NodeTree> m_nodeTree;
  bool m_updateNodeTree;

  IdType m_nextPersistentId = 1;

public:
  WorldNode(
    EntityPropertyConfig entityPropertyConfig, Entity entity, MapFormat mapFormat);
  WorldNode(
    EntityPropertyConfig entityPropertyConfig,
    std::initializer_list<EntityProperty> properties,
    MapFormat mapFormat);
  ~WorldNode() override;

  using Node::entityPropertyConfig;
  EntityPropertyConfig& entityPropertyConfig();

  MapFormat mapFormat() const;

  const NodeTree& nodeTree() const;

public: // layer management
  LayerNode* defaultLayer();

  const LayerNode* defaultLayer() const;

  /**
   * Returns defaultLayer() plus customLayers()
   */
  std::vector<LayerNode*> allLayers();

  /**
   * Returns defaultLayer() plus customLayers()
   */
  std::vector<const LayerNode*> allLayers() const;

  /**
   * Returns the custom layers in file order
   */
  std::vector<LayerNode*> customLayers();

  /**
   * Returns the custom layers in file order
   */
  std::vector<const LayerNode*> customLayers() const;

  /**
   * Returns defaultLayer() plus customLayers() ordered by LayerNode::sortIndex(). The
   * default layer is always first.
   */
  std::vector<LayerNode*> allLayersUserSorted();

  /**
   * Returns defaultLayer() plus customLayers() ordered by LayerNode::sortIndex(). The
   * default layer is always first.
   */
  std::vector<const LayerNode*> allLayersUserSorted() const;

  /**
   * Returns customLayers() ordered by LayerNode::sortIndex()
   */
  std::vector<LayerNode*> customLayersUserSorted();

  /**
   * Returns customLayers() ordered by LayerNode::sortIndex()
   */
  std::vector<const LayerNode*> customLayersUserSorted() const;

private:
  void createDefaultLayer();

public: // validator registration
  std::vector<const Validator*> registeredValidators() const;
  std::vector<const IssueQuickFix*> quickFixes(IssueType issueTypes) const;
  void registerValidator(std::unique_ptr<Validator> validator);
  void unregisterAllValidators();

public: // node tree bulk updating
  void disableNodeTreeUpdates();
  void enableNodeTreeUpdates();
  void rebuildNodeTree();

private:
  void invalidateAllIssues();

private: // implement Node interface
  const vm::bbox3d& doGetLogicalBounds() const override;
  const vm::bbox3d& doGetPhysicalBounds() const override;
  double doGetProjectedArea(vm::axis::type axis) const override;
  Node* doClone(const vm::bbox3d& worldBounds) const override;
  Node* doCloneRecursively(const vm::bbox3d& worldBounds) const override;
  bool doCanAddChild(const Node* child) const override;
  bool doCanRemoveChild(const Node* child) const override;
  bool doRemoveIfEmpty() const override;
  bool doShouldAddToSpacialIndex() const override;

  void doDescendantWasAdded(Node* node, size_t depth) override;
  void doDescendantWillBeRemoved(Node* node, size_t depth) override;
  void doDescendantPhysicalBoundsDidChange(Node* node) override;

  bool doSelectable() const override;
  void doPick(
    const EditorContext& editorContext,
    const vm::ray3d& ray,
    PickResult& pickResult) override;
  void doFindNodesContaining(const vm::vec3d& point, std::vector<Node*>& result) override;
  void doAccept(NodeVisitor& visitor) override;
  void doAccept(ConstNodeVisitor& visitor) const override;
  const EntityPropertyConfig& doGetEntityPropertyConfig() const override;

private: // implement EntityNodeBase interface
  void doPropertiesDidChange(const vm::bbox3d& oldBounds) override;
  vm::vec3d doGetLinkSourceAnchor() const override;
  vm::vec3d doGetLinkTargetAnchor() const override;

private: // implement Taggable interface
  void doAcceptTagVisitor(TagVisitor& visitor) override;
  void doAcceptTagVisitor(ConstTagVisitor& visitor) const override;

private:
  deleteCopyAndMove(WorldNode);
};

} // namespace tb::mdl
